﻿using System;
using System.Text;
using System.Collections.Generic;
using System.Threading;

using Heaven.Graphics;
using Heaven.Engine.Concurrency;
using Heaven.Mathematics;


namespace Heaven.Engine
{
    /// <summary>
    /// Renderer is a core object. It is responsible 
    /// for end-rendering of the content
    /// </summary>
    public class CoreRenderer : Node
    {
        #region Events

        /*/// <summary>
        /// Occurs when the renderer or one of its parts have been changed
        /// </summary>
        public event EventHandler Invalidated;*/

        #endregion

        #region Fields

        // Gets or sets the camera 
        // which is used by renderer
        Camera camera = null;
        // Render target
        RenderTarget target = null;
        // Node or collection of nodes as a source for rendering
        Node source = null;
        // This technique is used to render all source nodes
        Technique technique = null;

        // Render passes
        RendererCollection passes = new RendererCollection();

        // Render queue
        Dictionary<Technique, List<Visual>> renderQueue = new Dictionary<Technique, List<Visual>>();
        DeferredActions renderQueueActions = new DeferredActions();

        // Target color. This color will fill target buffer. 
        // In case of 'null' value no fill will be performed
        Color? targetColor = new Color(0, 0, 0, 0);
        // Target depth. This depth value will fill target buffer. 
        // In case of 'NaN' value no fill will be performed
        double targetDepth = 1.0;

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets target color. This color will fill target buffer. 
        /// In case of 'null' value no fill will be performed
        /// </summary>
        public Color? TargetColor
        {
            get { return targetColor; }
            set
            {
                targetColor = value;
                Invalidate();
            }
        }

        /// <summary>
        /// Gets or sets target depth. This depth value will fill target buffer. 
        /// In case of 'NaN' value no fill will be performed
        /// </summary>
        public double TargetDepth
        {
            get { return targetDepth; }
            set 
            {
                targetDepth = value;
                Invalidate();
            }
        }

        /// <summary>
        /// Render passes
        /// </summary>
        public RendererCollection Passes
        {
            get { return passes; }
            set
            {
                passes.Invalidated -= OnItemInvalidated;
                passes = value;
                passes.Invalidated += OnItemInvalidated;
                Invalidate();
            }
        }

        void OnItemInvalidated(object sender, EventArgs e)
        {
            Invalidate();
        }

        /// <summary>
        /// Gets or sets the camera which is used by renderer. 
        /// Null value indicates that camera have to be given 
        /// by the invoked renderer (if it is not primary renderer)
        /// </summary>
        public Camera Camera
        {
            get { return camera; }
            set
            {
                if (camera != null) camera.Invalidated -= OnItemInvalidated;
                camera = value;
                if (camera != null) camera.Invalidated += OnItemInvalidated;
                Invalidate();
            }
        }

        /// <summary>
        /// Render target. For the primary 
        /// renderer this value must be 'null'
        /// </summary>
        public RenderTarget Target
        {
            get { return target; }
            set
            {
                target = value;
                Invalidate();
            }
        }

        /// <summary>
        /// Gets or sets a node or collection 
        /// of nodes as a source for rendering
        /// </summary>
        public Node Source
        {
            get { return source; }
            set
            {
                if (source != null)
                {
                    renderQueueActions.Add(ClearDeferred);
                    source.Invalidated -= OnItemInvalidated;
                    source.Dettach(this);
                }
                source = value;
                if (source != null)
                {
                    source.Invalidated += OnItemInvalidated;
                    source.Attach(this);
                }
                Invalidate();
            }
        }
        
        /// <summary>
        /// Gets or sets override technique. 
        /// If this property is not null, this technique 
        /// is used to render all source nodes
        /// </summary>
        public Technique Technique
        {
            get { return technique; }
            set
            {
                if (technique != null) technique.Invalidated -= OnItemInvalidated;
                technique = value;
                if (technique != null) technique.Invalidated += OnItemInvalidated;
                Invalidate();
            }
        }

        #endregion

        #region Initialization

        /// <summary>
        /// Default constructor
        /// </summary>
        internal CoreRenderer()
        {
            passes.Invalidated += OnItemInvalidated;
        }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="target">Render target</param>
        /// <param name="source">Source nodes</param>
        /// <param name="camera">Camera</param>
        internal CoreRenderer(Node source, Camera camera) : this()
        {
            Source = source;
            Camera = camera;
        }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="target">Render target</param>
        /// <param name="source">Source nodes</param>
        /// <param name="camera">Camera</param>
        internal CoreRenderer(RenderTarget target, Node source, Camera camera) : this()
        {
            Target = target;
            Source = source;
            Camera = camera;
        }

        #endregion

        #region Deferred Actions

        void AddDeferred(object[] input)
        {
            List<Visual> list = null;
            if (!renderQueue.TryGetValue((Technique)input[1], out list))
            {
                list = new List<Visual>();
                renderQueue.Add((Technique)input[1], list);
            }
            Visual visual = (Visual)input[0];
            if(!list.Contains(visual)) list.Add(visual);            
        }

        void RemoveDeferred(object[] input)
        {
            List<Visual> list = null;
            if (renderQueue.TryGetValue((Technique)input[1], out list))
            {
                list.Remove((Visual)input[0]);
                if (list.Count == 0) renderQueue.Remove((Technique)input[1]);
            }
        }

        void ClearDeferred(object[] input)
        {
            renderQueue.Clear();
        }

        #endregion
                
        #region Methods

        /// <summary>
        /// Removes from the render queue
        /// </summary>
        /// <param name="visual">Visual</param>
        /// <param name="technique">It's technique</param>
        internal override void RemoveFromRenderQueue(Visual visual, Technique technique)
        {
            renderQueueActions.Add(RemoveDeferred, visual, technique);
        }

        /// <summary>
        /// Adds to the render queue
        /// </summary>
        /// <param name="visual">Visual</param>
        /// <param name="technique">It's technique</param>
        internal override void RegisterToRenderQueue(Visual visual, Technique technique)
        {
            renderQueueActions.Add(AddDeferred, visual, technique);
        }

        
        /// <summary>
        /// Enumerates nodes
        /// </summary>
        /// <returns>A System.Collections.Generic.IEnumerable<Visual> that 
        /// can be used to iterate through the collection</returns>
        internal override IEnumerable<Visual> Enumerate()
        {
            return source.Enumerate();
        }

        #endregion

        #region Rendering

        /// <summary>
        /// Renders current source with the given camera
        /// </summary>
        /// <param name="device">Graphics device</param>
        public void Render(IDevice device)
        {
            // Render dependent renders
            foreach (Renderer dependentRender in passes.Enumerate())
            {
                dependentRender.Render(device);
            }

            // Set current render target
            device.RenderTarget = target;

            // Clear
            if ((targetColor != null) && (!Double.IsNaN(targetDepth)))
            {
                device.DefaultColor = targetColor.Value;
                device.DefaultDepth = targetDepth;
                device.Clear();
            }
            else if (targetColor != null)
            {
                device.DefaultColor = targetColor.Value;
                device.ClearFrame();
            }
            else if (!Double.IsNaN(targetDepth))
            {
                device.DefaultDepth = targetDepth;
                device.ClearDepth();
            }


            // Update render queue
            renderQueueActions.Perform();

            // Rendering (grouping by techniques)            
            foreach (KeyValuePair<Technique, List<Visual>> renderItem in renderQueue)
            {
                Technique technique = renderItem.Key;

                // Set common parameters
                technique.SetCameraStandardAnnotations(device, camera);
                technique.Properties.UploadProperties(device, technique.Shader);


                // Render using the current techinque 
                // all nodes with the technique
                foreach (Visual visual in renderItem.Value)
                {
                    if ((!visual.IsHidden) && (visual.Polygonal != null))
                    {
                        technique.SetNodeStandardAnnotations(device, camera, visual);
                        visual.Properties.UploadProperties(device, technique.Shader);
                        device.Draw(visual.Polygonal, visual.Technique.Shader);
                    }
                }
            }
        }


        #endregion

        #region Debug

        /// <summary>
        /// Gets debug information
        /// </summary>
        /// <returns>Info</returns>
        public string GetStatistics()
        {
            StringBuilder result = new StringBuilder();
            result.AppendFormat("Techniques registered: {0}\n", renderQueue.Count);

            // Summ all visuals
            int visualsCount = 0;
            foreach(Technique technique in renderQueue.Keys)
            {
                visualsCount += renderQueue[technique].Count;
            }

            result.AppendFormat("Visuals total: {0}\n", visualsCount);

            return result.ToString();
        }

        #endregion
    }

    /*/// <summary>
    /// Renderer is a core object. It is responsible 
    /// for end-rendering of the content
    /// </summary>
    public class CoreRenderer
    {
        #region Events

        /// <summary>
        /// Occurs when the renderer or one of its parts have been changed
        /// </summary>
        public event EventHandler Invalidated;

        #endregion

        #region Fields

        // Gets or sets the camera 
        // which is used by renderer
        Camera camera = null;
        // Render target
        RenderTarget target = null;
        // Node or collection of nodes as a source for rendering
        Node source = null;
        // This technique is used to render all source nodes
        Technique technique = null;

        // Render passes
        RendererCollection passes = new RendererCollection();

        // Cache of nodes
        Visual[] nodeCache = new Visual[10000];
        bool[] nodeRenderedCache = new bool[10000];
        int nodeCacheSize = 0;

        #endregion

        #region Properties

        /// <summary>
        /// Render passes
        /// </summary>
        public RendererCollection Passes
        {
            get { return passes; }
            set
            {
                passes.Invalidated -= OnItemInvalidated;
                passes = value;
                passes.Invalidated += OnItemInvalidated;
                Invalidate();
            }
        }

        void OnItemInvalidated(object sender, EventArgs e)
        {
            Invalidate();
        }

        /// <summary>
        /// Gets or sets the camera which is used by renderer. 
        /// Null value indicates that camera have to be given 
        /// by the invoked renderer (if it is not primary renderer)
        /// </summary>
        public Camera Camera
        {
            get { return camera; }
            set 
            {
                if (camera != null) camera.Invalidated -= OnItemInvalidated;
                camera = value;
                if (camera != null) camera.Invalidated += OnItemInvalidated;
                Invalidate();
            }
        }

        /// <summary>
        /// Render target. For the primary 
        /// renderer this value must be 'null'
        /// </summary>
        public RenderTarget Target
        {
            get { return target; }
            set 
            {
                target = value;
                Invalidate();
            }
        }

        /// <summary>
        /// Gets or sets a node or collection 
        /// of nodes as a source for rendering
        /// </summary>
        public Node Source
        {
            get { return source; }
            set 
            {
                if (source != null)
                {
                    source.Invalidated -= OnItemInvalidated;
                    source.Dettach();
                }
                source = value;
                if (source != null)
                {
                    source.Invalidated += OnItemInvalidated;
                    source.Attach();
                }
                Invalidate();
            }
        }

        /// <summary>
        /// Gets or sets override technique. 
        /// If this property is not null, this technique 
        /// is used to render all source nodes
        /// </summary>
        public Technique Technique
        {
            get { return technique; }
            set 
            {
                if (technique != null) technique.Invalidated -= OnItemInvalidated;
                technique = value;
                if (technique != null) technique.Invalidated += OnItemInvalidated;
                Invalidate();
            }
        }

        #endregion

        #region Initialization

        /// <summary>
        /// Default constructor
        /// </summary>
        internal CoreRenderer()
        {
            passes.Invalidated += OnItemInvalidated;
        }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="target">Render target</param>
        /// <param name="source">Source nodes</param>
        /// <param name="camera">Camera</param>
        internal CoreRenderer(Node source, Camera camera)  : this()
        {
            Source = source;
            Camera = camera;
        }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="target">Render target</param>
        /// <param name="source">Source nodes</param>
        /// <param name="camera">Camera</param>
        internal CoreRenderer(RenderTarget target, Node source, Camera camera) : this()
        {
            Target = target;
            Source = source;
            Camera = camera;
        }

        #endregion

        #region Methods

        /// <summary>
        /// Invalidates object
        /// </summary>
        public void Invalidate()
        {
            if (Invalidated != null) Invalidated(this, EventArgs.Empty);
        }

        #endregion

        #region Rendering

        /// <summary>
        /// Renders current source with the given camera
        /// </summary>
        /// <param name="device">Graphics device</param>
        internal void Render(IDevice device)
        {
            // Render dependent renders
            foreach (Renderer dependentRender in passes.Enumerate())
            {
                dependentRender.Render(device);
            }

            // Set current render target
            device.RenderTarget = target;
            device.Clear();

            // Fill the cache
            nodeCacheSize = 0;
            foreach (Visual node in source.Enumerate())
            {
                nodeCache[nodeCacheSize] = node;
                nodeRenderedCache[nodeCacheSize] = false;
                nodeCacheSize++;
            }

            // There is nothing to render
            if (nodeCacheSize == 0) return;

            // Rendering (grouping by techniques)
            Heaven.Engine.Technique lastUsedTechnique = nodeCache[0].Technique;
            int lastUsedItem = 0;
            while (true)
            {
                if (lastUsedTechnique != null)
                {
                    // Set common parameters
                    lastUsedTechnique.SetCameraStandardAnnotations(device, camera);
                    lastUsedTechnique.Properties.UploadProperties(device, lastUsedTechnique.Shader);
                }

                // Render using the current techinque 
                // all nodes with the technique
                for (int i = lastUsedItem; i < nodeCacheSize; i++)
                {
                    Visual item = nodeCache[i];
                    if (item.Technique != lastUsedTechnique) continue;
                    
                    if ((!item.IsHidden) && (item.Polygonal != null))
                    {
                        lastUsedTechnique.SetNodeStandardAnnotations(device, camera, item);
                        item.Properties.UploadProperties(device, lastUsedTechnique.Shader);
                        device.Draw(item.Polygonal, item.Technique.Shader);
                    }
                    nodeRenderedCache[i] = true;
                }

                // Correct last used item
                bool renderingEnded = true;
                for (int i = lastUsedItem + 1; i < nodeCacheSize; i++)
                {
                    if (nodeRenderedCache[i]) continue;
                    if (nodeCache[i].Technique != lastUsedTechnique)
                    {
                        lastUsedItem = i;
                        lastUsedTechnique = nodeCache[i].Technique;
                        renderingEnded = false;
                        break;
                    }
                }
                // There is no more to render
                if (renderingEnded) break;
            }
        }
                

        #endregion

        
    }*/

    /*
    /// <summary>
    /// Renderer is a core object. It is responsible 
    /// for end-rendering of the content
    /// </summary>
    public class OldRenderer : GenericCollection<RenderQueue>
    {
        #region Events


        #endregion

        #region Fields

        // Shadow renderer
        ShadowRenderer shadowRenderer = null;

        #endregion

        #region Properties

        #endregion

        #region Initialization

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="device">Graphics device</param>
        public Renderer(IDevice device)
        {
            
            this.shadowRenderer = new ShadowRenderer(device);

//            GlobalInvalidated += delegate { invalidatedEvent.Set(); };
        }
               

        #endregion

        #region Methods

        /// <summary>
        /// Invalidates current content
        /// </summary>
        public void Invalidate()
        {
            shadowRenderer.Invalidate();   
        }

        /// <summary>
        /// Adds an item
        /// </summary>
        /// <param name="renderQueue">Render queue</param>
        public override void Add(RenderQueue renderQueue)
        {
            base.Add(renderQueue);
            shadowRenderer.Add(renderQueue);
            renderQueue.Invalidated += new EventHandler(OnRenderQueueInvalidated);
        }

        void OnRenderQueueInvalidated(object sender, EventArgs e)
        {
            shadowRenderer.Invalidate();
        }

        /// <summary>
        /// Removes the given item
        /// </summary>
        /// <param name="renderQueue">Render queue</param>
        public override void Remove(RenderQueue renderQueue)
        {
            renderQueue.Invalidated -= new EventHandler(OnRenderQueueInvalidated);
            base.Remove(renderQueue);
            shadowRenderer.Remove(renderQueue);
        }

        /// <summary>
        /// Shutdowns rendering process
        /// </summary>
        public void Shutdown()
        {
            shadowRenderer.Shutdown();
        }

        #endregion
    }

    /// <summary>
    /// Shadow renderer is async part of renderer
    /// </summary>
    class ShadowRenderer : DeferredCollection<RenderQueue>
    {
        #region Fields

        // Graphics device
        WeakReference deviceReference;
        // Indicates that the current content has been invalidated
        AutoResetEvent invalidatedEvent = new AutoResetEvent(true);
        // Require that renderer to be shuted down
        AutoResetEvent shutdownEvent = new AutoResetEvent(false);
        // Indicates that renderer has been shuted down
        ManualResetEvent shutdownCompleteEvent = new ManualResetEvent(false);
        // Indicates that renderer can process
        ManualResetEvent canProcessEvent = new ManualResetEvent(true);
        // Indicates that renderer has been finished rendering
        ManualResetEvent finishedProcessEvent = new ManualResetEvent(true);

        #endregion

        #region Initialization

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="device">Graphics device</param>
        public ShadowRenderer(IDevice device)
        {
            this.deviceReference = new WeakReference(device);
            device.AvailabilityChanged += new EventHandler(OnDeviceAvailabilityChanged);

            // Initialize render-thread
            Thread thread = new Thread(Async);
            thread.Priority = ThreadPriority.Normal;
            thread.Name = "Asynchronous rendering";
            thread.IsBackground = true; // FIXME:
            thread.Start();

            Invalidated += new EventHandler(OnShadowRendererInvalidated);
        }

        void OnDeviceAvailabilityChanged(object sender, EventArgs e)
        {
            IDevice device = (IDevice)deviceReference.Target;
            if (device == null) return;
            if (device.IsAvailable) canProcessEvent.Set();
            else canProcessEvent.Reset();
        }

        void OnShadowRendererInvalidated(object sender, EventArgs e)
        {
            invalidatedEvent.Set();
        }

        /// <summary>
        /// Destructor
        /// </summary>
        ~ShadowRenderer() { shutdownEvent.Set(); }

        #endregion
                
        #region Properties

        #endregion

        #region Methods

        /// <summary>
        /// Shutdowns rendering process
        /// </summary>
        public void Shutdown()
        {            
            GC.SuppressFinalize(this);
            shutdownEvent.Set();
            canProcessEvent.Set();
            shutdownCompleteEvent.WaitOne();            
        }

        /// <summary>
        /// Methods end when rendering has finished
        /// </summary>
        public void Redraw()
        {
            finishedProcessEvent.Reset();
            invalidatedEvent.Set();
            finishedProcessEvent.WaitOne();
        }

        #endregion

        #region Asynchronous update

        // Asynchronous update
        void Async()
        {
            WaitHandle[] invalidateAndShutdown = new WaitHandle[] { invalidatedEvent, shutdownEvent };
            IDevice device = null;

            do
            {
                try
                {
                    if (IsChanged)
                    {
                        // Perform deferred actions 
                        // under the collection
                        Perform();
                    }

                    finishedProcessEvent.Reset();
                    device = (IDevice)deviceReference.Target;
                    if (device == null) return;
                    Render(device);
                    device = null;
                    finishedProcessEvent.Set();
                }
                catch (Exception exception) 
                { 
                    // FIXME: add exception handler
                    //return; 
                }
                
                // Is device available?
                canProcessEvent.WaitOne();

                if (WaitHandle.WaitAny(invalidateAndShutdown) == 1)
                {
                    // If shutdown event is fired
                    shutdownCompleteEvent.Set();
                    return;
                }
            }
            while (true);
        }

        void Render(IDevice device)
        {
            try
            {
                device.Begin();
                foreach (RenderQueue queue in this)
                {
                    foreach (RenderGroup group in queue.Shadow)
                    {
                        group.Draw(device);
                    }
                }
            }
            catch (Exception exception) { System.Diagnostics.Debug.WriteLine("Renderer error: " + exception.Message + " \nStack: \n" + exception.StackTrace); throw; }
            finally
            {
                device.Present();
            }
        }

        #endregion
    }*/
}
